package top.mcwebsite.playandroid.common.network.impl.cookie

import android.content.SharedPreferences
import android.util.Log
import okhttp3.Cookie
import okhttp3.HttpUrl
import top.mcwebsite.playandroid.common.api.AppService
import top.mcwebsite.playandroid.lib.servicemanager.core.ServiceManager
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.*
import java.util.concurrent.ConcurrentHashMap


object PersistentCookieStore {

    private const val LOG_TAG = "PersistentCookieStore"
    private const val COOKIE_PREFS = "PersistentCookieStore"
    private const val COOKIE_NAME_PREFIX = "cookie_"
    private const val HOST_KEYS = "hosts"

    private val cookies = ConcurrentHashMap<String, ConcurrentHashMap<String, Cookie>>()
    private val cookiePrefs: SharedPreferences =
        ServiceManager.getService<AppService>().getApplication()
            .getSharedPreferences(COOKIE_PREFS, 0)


    init {
        val hostKeys = cookiePrefs.getStringSet(HOST_KEYS, mutableSetOf())!!
        for (hostKey in hostKeys) {
            val cookieNames = cookiePrefs.getStringSet(hostKey, mutableSetOf())!!
            if (cookieNames.isNotEmpty()) {
                cookies[hostKey] = ConcurrentHashMap()
            }
            for (name in cookieNames) {
                val encodeCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null)
                if (encodeCookie != null) {
                    val decodeCookie = decodeCookie(encodeCookie)
                    if (decodeCookie != null) {
                        cookies[hostKey]!![name] = decodeCookie
                    }
                }
            }
            if (cookieNames.isNotEmpty() && cookies[hostKey]!!.isEmpty()) {
                cookies.remove(hostKey)
            }
        }
        clearExpired(Date())
    }

    fun get(httpUrl: HttpUrl): List<Cookie> {
        val host = httpUrl.host()
        return cookies[host]?.values?.filter {
            it.expiresAt() > System.currentTimeMillis()
        }?.toList() ?: emptyList()
    }

    fun addCookies(httpUrl: HttpUrl, cookies: List<Cookie>) {
        cookies.forEach {
            addCookie(httpUrl, it)
            Log.d("mengchen", it.toString())
        }
    }

    fun addCookie(httpUrl: HttpUrl, cookie: Cookie) {
        if (!cookie.persistent()) {
            return
        }
        val name = cookie.name() + cookie.domain()
        val host = httpUrl.host()
        if (!cookies.containsKey(host)) {
            cookies[host] = ConcurrentHashMap()
        }
        if (cookie.expiresAt() < System.currentTimeMillis()) {
            cookies[host]?.remove(name)
        } else {
            cookies[host]?.put(name, cookie)
        }

        // Save cookie into persistent store
        val prefsWrite = cookiePrefs.edit()
        prefsWrite.putStringSet(HOST_KEYS, cookies.keys)
        prefsWrite.putStringSet(host, cookies[host]?.keys)
        prefsWrite.putString(COOKIE_NAME_PREFIX + name, encodeCookie(SerializableCookie(cookie)))
        prefsWrite.apply()
    }

    fun clear() {
        val prefsWriter = cookiePrefs.edit()
        for (host in cookies.keys) {
            prefsWriter.remove(host)
            for (cookieName in cookies[host]!!.keys) {
                prefsWriter.remove(cookieName)
            }
            prefsWriter.remove(host)
        }
        prefsWriter.remove(HOST_KEYS)
        prefsWriter.apply()
        cookies.clear()
    }

    fun clearExpired(date: Date): Boolean {
        var clearAny = false
        val prefsWriter = cookiePrefs.edit()
        for ((host, cookies) in cookies) {
            for (entry in cookies) {
                val name = entry.key
                val cookie = entry.value
                if (cookie.expiresAt() < date.time) {
                    cookies.remove(name)
                    prefsWriter.remove(COOKIE_NAME_PREFIX + name)
                    clearAny = true
                }
            }
            if (clearAny) {
                prefsWriter.putStringSet(HOST_KEYS, this.cookies[host]?.keys)
            }
        }
        prefsWriter.apply()
        return clearAny
    }

    fun getCookies() = cookies.values.toList()

    fun deleteCookie(cookie: Cookie) {
        val name: String = cookie.name() + cookie.domain()
        cookies.remove(name)
        val prefsWriter = cookiePrefs.edit()
        prefsWriter.remove(COOKIE_NAME_PREFIX + name)
        prefsWriter.apply()
    }

    fun encodeCookie(cookie: SerializableCookie): String? {
        val os = ByteArrayOutputStream()
        try {
            val outputStream = ObjectOutputStream(os)
            outputStream.writeObject(cookie)
        } catch (e: IOException) {
            Log.d(LOG_TAG, "IOException in encodeCookie", e)
            return null
        }

        return byteArrayToHexString(os.toByteArray())
    }

    fun decodeCookie(cookieString: String): Cookie? {
        val bytes: ByteArray = hexStringToByteArray(cookieString)
        val byteArrayInputStream = ByteArrayInputStream(bytes)
        var cookie: Cookie? = null
        try {
            val objectInputStream = ObjectInputStream(byteArrayInputStream)
            cookie = (objectInputStream.readObject() as SerializableCookie).getCookie()
        } catch (e: IOException) {
            Log.d(LOG_TAG, "IOException in decodeCookie", e)
        } catch (e: ClassNotFoundException) {
            Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e)
        }
        return cookie
    }

    /**
     * 二进制数组转十六进制字符串
     *
     * @param bytes byte array to be converted
     * @return string containing hex values
     */
    private fun byteArrayToHexString(bytes: ByteArray): String {
        val sb = StringBuilder(bytes.size * 2)
        for (element in bytes) {
            val v = element.toInt() and 0xff
            if (v < 16) {
                sb.append('0')
            }
            sb.append(Integer.toHexString(v))
        }
        return sb.toString().uppercase(Locale.US)
    }

    /**
     * 十六进制字符串转二进制数组
     *
     * @param hexString string of hex-encoded values
     * @return decoded byte array
     */
    private fun hexStringToByteArray(hexString: String): ByteArray {
        val len = hexString.length
        val data = ByteArray(len / 2)
        var i = 0
        while (i < len) {
            data[i / 2] = ((Character.digit(hexString[i], 16) shl 4) + Character.digit(hexString[i + 1], 16)).toByte()
            i += 2
        }
        return data
    }
}
